React Ink
components
いろいろある
色々詰め合わせ
ascii artによるグラフ
renderのoptions
(since v6.5.0)incrementalRendering: true:CLIを毎回すべて再描画するのではなく、変更があった箇所だけ再描画する examples
充実している
<Suspense />を使った例などもある
code:concurrent-suspense.tsx
/** @jsxRuntime automatic */
/** @jsxImportSource npm:react@19 */
/** @jsxImportSourceTypes npm:@types/react@19 */
import {
type FunctionComponent,
Suspense,
use,
useEffect,
useMemo,
useState,
} from "npm:react@19";
import { Box, render, Text } from "npm:ink@6.8";
import { delay } from "jsr:@std/async@1/delay";
/** Simulated async data fetching with cache */
const cache = new Map<string, Promise<string>>();
const fetchData = (key: string, delayMs: number): Promise<string> => {
{
const promise = cache.get(key);
if (promise) return promise;
}
const promise = delay(delayMs).then(() => Data for "${key}" (fetched in ${delayMs}ms));
cache.set(key, promise);
return promise;
};
/** Component that suspends while fetching */
const DataItem: FunctionComponent<{ readonly dataPromise: Promise<string> }> = (
{
dataPromise,
},
) => {
const data = use(dataPromise);
return (
<Box marginLeft={2}>
<Text color="green">{data}</Text>
</Box>
);
};
/** Loading fallback */
const Loading: FunctionComponent<{ readonly message: string }> = (
{ message },
) => (
<Box marginLeft={2}>
<Text color="yellow">{message}</Text>
</Box>
);
/** Main app demonstrating concurrent suspense */
const App: FunctionComponent = () => {
const fastPromise = useMemo(() => fetchData("fast", 200), []);
const mediumPromise = useMemo(() => fetchData("medium", 800), []);
const slowPromise = useMemo(() => fetchData("slow", 1500), []);
Promise<string> | null
();
// Auto-trigger "show more" after 2 seconds
useEffect(() => {
const timer = setTimeout(() => {
setDynamicPromise(fetchData("dynamic", 500));
}, 2000);
return () => {
clearTimeout(timer);
};
}, []);
return (
<Box flexDirection="column">
<Text bold underline>
Concurrent Suspense Demo
</Text>
<Text dimColor>
(With concurrent: true, Suspense re-renders automatically)
</Text>
<Box marginTop={1} />
<Text>Medium data (800ms):</Text>
<Suspense fallback={<Loading message="Loading medium data..." />}>
<DataItem dataPromise={mediumPromise} />
</Suspense>
<Box marginTop={1} />
<Text>Fast data (200ms):</Text>
<Suspense fallback={<Loading message="Loading fast data..." />}>
<DataItem dataPromise={fastPromise} />
</Suspense>
<Box marginTop={1} />
<Text>Slow data (1500ms):</Text>
<Suspense fallback={<Loading message="Loading slow data..." />}>
<DataItem dataPromise={slowPromise} />
</Suspense>
{dynamicPromise && (
<>
<Box marginTop={1} />
<Text>Dynamically added (500ms):</Text>
<Suspense fallback={<Loading message="Loading dynamic data..." />}>
<DataItem dataPromise={dynamicPromise} />
</Suspense>
</>
)}
</Box>
);
};
// Render with concurrent mode enabled
render(<App />, { concurrent: true });